import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, TemplateRef, ViewChild } from '@angular/core';
import { NzDropdownContextComponent, NzDropdownService, NzFormatEmitEvent, NzTreeComponent, NzTreeNode, NzTreeNodeOptions, NzNotificationService } from 'ng-zorro-antd';
import { IdeService } from '../ide.service';
import { PatternService } from '../../../public/service/pattern.service';
import { Setting } from '../../../public/setting/setting';
import { FurtherData, Tab } from '../../../public/setting/model';
import { CodeType, HttpCodesEnum } from '../../../public/setting/enums';
import { Util } from '../../../public/util/util';

@Component({
  selector: 'app-menu-tree',
  templateUrl: './menu-tree.component.html',
  styleUrls: ['./menu-tree.component.scss']
})
export class MenuTreeComponent implements OnInit, OnChanges {

  constructor(private nzDropdownService: NzDropdownService,
    private notification: NzNotificationService,
    private ideService: IdeService) {
  }

  @Input() showMenu = true;
  @Input() ifBindDevice: any;
  @Input() addMenu = '';
  @Input() selectedLines = '';

  @Output() whenOpenFile = new EventEmitter();
  @Output() afterAddMenu = new EventEmitter();
  @Output() afterDeleteMenu = new EventEmitter();
  @Output() afterRenameMenu = new EventEmitter();
  @Output() afterBindDevice = new EventEmitter();
  @Output() afterLoadFurther = new EventEmitter();

  @ViewChild('treeCom') treeCom: NzTreeComponent;
  @ViewChild('RenameInput') renameInput: ElementRef;
  @ViewChild('FileInput') fileInput: ElementRef;
  @ViewChild('FolderInput') folderInput: ElementRef;
  @ViewChild('BindDevice') bindDeviceInput: ElementRef;

  public activedNode: NzTreeNode; // actived node
  public nodes: Array<any> = [];
  public nodeOrigin: any;
  public insertNode: NzTreeNode;
  public historyMenu: NzTreeNode[];
  public isRenameFile = false;
  public isNewFile = false;
  public isNewFolder = false;
  public isBindDevice = false;
  public codeTypes = CodeType; // code type enum
  public oldFileName: string;
  public fileName: string;
  public newFileName = ''; // file name
  public newFolderName = ''; // folder name
  public token: string;
  public fileType: string = this.codeTypes.python3; // default file type
  public fileNameInvalid = false;
  public bindDeviceInvalid = false;

  private projectDataFromFurther: FurtherData;
  private dropdown: NzDropdownContextComponent;

  static pushToParent(parent, pathArr, idx, item) {
    let obj;
    // Query whether the child has this objec
    if (parent.children) {
      for (obj of parent.children) {
        if (obj.title === pathArr[idx]) {
          return obj;
        }
      }
    }

    // If the child does not have this object, add it
    obj = {
      title: pathArr[idx],
      path: pathArr.slice(0, idx + 1).join('/'),
      key: pathArr.slice(0, idx + 1).join('/'),
      isLeaf: (idx === pathArr.length - 1) && (Boolean(item.codeType) || Boolean(item.id)),
    };
    if (obj.isLeaf) {
      obj.content = item.content;
      obj.codeType = item.codeType ? item.codeType : obj.title.indexOf('.py') ? CodeType.python3 : CodeType.plain;
      obj.id = item.id;
    }
    if (!parent.children) {
      parent.children = [];
    }
    parent.children.push(obj);
    return obj;
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes && changes['ifBindDevice']) {
      if (this.ifBindDevice) {
        this.showBindDeviceModel();
      }
    }
    if (changes && changes['addMenu'] && this.addMenu !== '') {
      this.insertNode = null; // There may have been a call-right before it was created, so leave insertNode empty to prevent file directory errors
      if (this.addMenu === 'file') {
        this.createNewFile();
      }
      if (this.addMenu === 'folder') {
        this.createNewFolder();
      }
    }
  }

  ngOnInit() {
    if (localStorage.getItem(Setting.storage.accessToken)) {
      this.loadProjectsDataFromFurther();
    }
  }

  loadProjectsDataFromFurther() {
    this.ideService.loadProjectsFromFurther()
      .subscribe(
        (res: FurtherData) => {
          this.projectDataFromFurther = res;
          Setting.projectVersionId = res.id;
          if (this.projectDataFromFurther.code.length > 0) {
            this.getMenuTree();
          }
          // this.signInFurther(this.projectDataFromFurther); // TODO
          this.afterLoadFurther.emit({ data: this.projectDataFromFurther });
          localStorage.setItem(Setting.storage.furtherMenu, JSON.stringify(this.projectDataFromFurther.code));
        })
  }

  signInFurther(furtherData: FurtherData) {
    const data = {
      accountId: furtherData.account.id,
      email: furtherData.account.email,
      username: furtherData.account.username
    };
    this.ideService.signInByFurtherToken(data).subscribe(res => {
      if(res.success){
        if (this.projectDataFromFurther.code.length > 0) {
          this.getMenuTree();
        } // Gets the tree structure
        let deviceData = localStorage.getItem(Setting.storage.deviceData);
        if (deviceData) {
          deviceData = JSON.parse(deviceData);
          this.afterBindDevice.emit({ deviceData: deviceData });
        } else {
          this.showBindDeviceModel();
        }
      }
    });
  }

  getMenuTree() {
    let parent = {},
      tree = { path: 'root', children: [] };
    this.projectDataFromFurther.code.forEach((item) => {
      const pathArr: Array<string> = item.path.split('/');
      pathArr.forEach((path, idx) => {
        if (idx === 0) {
          parent = tree;
        }
        parent = MenuTreeComponent.pushToParent(parent, pathArr, idx, item);
      });
    });
    this.nodes = tree.children.sort(Util.compare('title', true));
    if (Setting.codeId) {
      setTimeout(_ => {
        this.openDefaultFile(this.treeCom.nzNodes);
      });
    }
  }

  createBlockInFurther() {
    const codeId = this.activedNode.origin.id;
    window.open(`${Setting.furtherFrontendUrl}/projects/${this.projectDataFromFurther.projectId}` +
      `/edit?access_token=${this.ideService.accessToken}&createBlock=${codeId}&lines=${this.selectedLines}`);
  }

  /**
   * Open default file（file id from further）
   * @param nodes
   */
  openDefaultFile(nodes: NzTreeNode[]) {
    nodes.forEach(node => {
      if (node.origin.id && node.origin.id === Setting.codeId) {
        this.whenOpenFile.emit(node.origin);
        this.expandParentNode(node);
        this.activedNode = node;
      } else if (node.children && node.children.length > 0) {
        this.openDefaultFile(node.children);
      }
    });
  }

  expandParentNode(node: NzTreeNode) {
    if (node.parentNode) {
      node.parentNode.isExpanded = true;
      if (node.parentNode.parentNode) {
        this.expandParentNode(node.parentNode);
      }
    }
  }

  showBindDeviceModel() {
    this.isBindDevice = true;
    setTimeout(_ => {
      this.bindDeviceInput.nativeElement.focus();
    }, 300);
  }

  /**
   * save active menu to local
   */
  saveActivedMenu() {
    localStorage.setItem(Setting.storage.curActivedMenu, JSON.stringify(this.activedNode.origin));
  }

  /**
   * open folder
   * @param data
   */
  openFolder(data: NzTreeNode | NzFormatEmitEvent): void {
    if (data instanceof NzTreeNode) {
      data.isExpanded = !data.isExpanded;
    } else {
      data.node.isExpanded = !data.node.isExpanded;
      if (data.node.isLeaf) {
        this.whenOpenFile.emit(data.node.origin);
      }
    }
  }

  /**
   * active node
   * @param data
   */
  activeNode(data: NzTreeNode | NzFormatEmitEvent): void {
    if (data instanceof NzTreeNode) {
      data.isExpanded = !data.isExpanded;
    } else {
      data.node.isExpanded = !data.node.isExpanded;
      data.node.isSelected = true;
      this.activedNode = data.node;
      this.treeCom.nzTreeService.setNodeActive(this.activedNode);
      this.saveActivedMenu(); // save active menu
      if (data.node.isLeaf) {
        this.whenOpenFile.emit(data.node.origin);
      }
    }
  }

  bindDevice(event?) {
    if (event) {
      event.preventDefault();
    }
    const data = {
      token: this.token,
    };
    this.ideService.bindDevice(data).subscribe((res: any) => {
      this.token = '';
      this.isBindDevice = false;
      if (res && res.success) {
        localStorage.setItem(Setting.storage.deviceData, JSON.stringify(res.data));
        this.afterBindDevice.emit({ deviceData: res.data });
        this.ideService.deviceIp = `${res.data.ip}:${res.data.port}`;
      }else{
        if (res && res.code === HttpCodesEnum.offline) {
          // localStorage.removeItem(Setting.storage.deviceData);
        }
      }
    }
    );
  }

  /**
   * context menu
   * @param $event
   * @param template
   * @param data
   */
  contextMenu($event: MouseEvent, template: TemplateRef<void>, data: NzTreeNode): void {
    if (!this.projectDataFromFurther.canEdit) {
      return;
    }
    this.dropdown = this.nzDropdownService.create($event, template);
    this.nodeOrigin = data.origin;
    this.insertNode = data;
  }

  createNewFolder(isDropDown: boolean = false) {
    this.isNewFolder = true;
    setTimeout(_ => {
      this.folderInput.nativeElement.focus();
    }, 300);
    if (isDropDown) {
      this.dropdown.close();
    }
  }

  createNewFile(isDropDown: boolean = false) {
    this.isNewFile = true;
    setTimeout(_ => {
      this.fileInput.nativeElement.focus();
    }, 300);
    if (isDropDown) {
      this.dropdown.close();
    }
  }

  renameFile(event): void {
    event.preventDefault();
    this.fileName = this.nodeOrigin.title;
    this.oldFileName = this.nodeOrigin.title;
    this.dropdown.close();
    this.showRenameModal();
  }

  showRenameModal(): void {
    this.isRenameFile = true;
    let endIdx: number;
    if (this.fileName.split('.').length >= 2 && this.fileName.indexOf('.') !== 0) {
      endIdx = this.fileName.lastIndexOf('.');
    } else {
      endIdx = this.fileName.length;
    }
    setTimeout(_ => {
      this.renameInput.nativeElement.focus();
      this.renameInput.nativeElement.setSelectionRange(0, endIdx);
    });
  }

  onInputBlur() {
    if (this.isRenameFile) {
      this.renameInput.nativeElement.focus();
    }
  }

  onNameInputBlur() {
    if (this.isNewFile) {
      this.fileInput.nativeElement.focus();
    }
  }

  onFolderInputBlur() {
    if (this.isNewFolder) {
      this.folderInput.nativeElement.focus();
    }
  }

  inputFileName(event) {
    this.checkFileValueInvalid(this.newFileName);
    if (event && event.keyCode === 13) {
      this.addFile();
    }
  }

  inputFolder(event) {
    this.checkFolderValueInvalid(this.newFolderName);
    if (event && event.keyCode === 13) {
      this.addFolder();
    }
  }

  inputRenameFile(event) {
    if (this.insertNode.isLeaf) {
      this.checkFileValueInvalid(this.fileName);
    } else {
      this.checkFolderValueInvalid(this.fileName);
    }
    if (event && event.keyCode === 13) {
      this.saveRename();
    }
  }

  inputTokenCode(event) {
    if (event && event.keyCode === 13) {
      this.bindDevice(event);
    }
  }

  checkFileValueInvalid(value) {
    this.fileNameInvalid = value && !PatternService.FILENAME_REGEXP.test(value);
  }

  checkFolderValueInvalid(value) {
    this.fileNameInvalid = value && !PatternService.FOLDERNAME_REGEXP.test(value);
  }

  handleCancel(): void {
    this.isRenameFile = false;
    this.isNewFile = false;
    this.isNewFolder = false;
    this.isBindDevice = false;
    this.fileNameInvalid = false;
    this.afterAddMenu.emit('');
  }

  addFile(event?) {
    if (event) {
      event.preventDefault();
    }
    if (this.newFileName === '' || this.fileNameInvalid) {
      return;
    }
    let path = this.insertNode ? this.insertNode.origin.path : '';
    path = path ? (path + '/').replace('//', '/') + this.newFileName : this.newFileName;
    let data = {
      path: path,
      fileType: this.fileType,
      content: ``
    };
    this.insertNodeToTree({
      title: this.newFileName,
      path: data.path,
      key: path,
      isLeaf: true,
      content: data.content,
      codeType: this.fileType
    });
    this.sort(); // sort menu

    // active the new file and open it in editor
    const curNode = this.insertNode ?
      this.insertNode.children.find(node => node.origin.path === data.path) :
      this.treeCom.nzNodes.find(node => node.origin.path === data.path);
    this.activedNode = curNode;
    this.treeCom.nzTreeService.setNodeActive(curNode);
    this.whenOpenFile.emit(curNode.origin); // output the new file node to parent component then open it

    // reset defines
    this.afterAddMenu.emit('');
    this.isNewFile = false;
    this.newFileName = '';
    this.insertNode = null;
    this.saveActivedMenu(); // 存储激活状态的菜单
  }

  addFolder(event?) {
    if (event) {
      event.preventDefault();
    }
    if (this.newFolderName === '' || this.fileNameInvalid) {
      return;
    }
    const path = this.insertNode ? this.insertNode.origin.path + '/' + this.newFolderName : this.newFolderName;
    this.insertNodeToTree({
      title: this.newFolderName,
      path: path,
      key: path,
      isLeaf: false,
    });
    this.sort(); // sort menu

    // reset defines
    this.newFolderName = '';
    this.isNewFolder = false;
    this.afterAddMenu.emit('');
  }

  insertNodeToTree(data: NzTreeNodeOptions): void {
    if (this.insertNode) {
      if (this.insertNode.children) {
        this.insertNode.addChildren([data]);
        this.insertNode.isExpanded = true;
      } else {
        this.insertNode.children = [new NzTreeNode(data)];
        this.insertNode.isExpanded = true;
      }
    } else {
      this.treeCom.nzNodes.push(new NzTreeNode(data));
    }
  }

  saveRename() {
    if (this.fileName === '' || this.fileNameInvalid) {
      return;
    }
    let beforePath = this.insertNode.origin.path;
    if (beforePath.lastIndexOf('/') === beforePath.length - 1) {
      beforePath = beforePath.substring(0, beforePath.length - 1);
    }
    const newPath = beforePath.substring(0, beforePath.lastIndexOf('/') + 1) + this.fileName;
    const data = {
      oldPath: beforePath,
      newPath: newPath,
    };
    this.isRenameFile = false;
    this.changeCurNode(data); // change current node
    if (!this.insertNode.isLeaf) {
      this.changeChildrenNodes(this.insertNode, data); // change child nodes
    }
    this.insertNode && this.insertNode.parentNode ?
      this.insertNode.parentNode.children.sort(Util.compare('title', true)) :
      this.treeCom.nzNodes.sort(Util.compare('title', true));
    this.insertNode = null;
  }

  changeCurNode(file) {
    this.insertNode.origin.title = this.fileName;
    this.insertNode.origin.key = file.newPath;
    this.insertNode.origin.path = file.newPath;
    this.insertNode.title = this.fileName;
    this.insertNode.key = file.newPath;
    if (this.insertNode.isLeaf) {
      this.renameFurtherFile(this.insertNode, file.oldPath);
    }
  }

  changeChildrenNodes(node: NzTreeNode, data) {
    if (node.children && node.children.length > 0) {
      node.children.forEach(item => {
        const oldPath = item.origin.path;
        const newPath = item.origin.path.replace(data.oldPath, data.newPath);
        item.key = newPath;
        item.origin.key = newPath;
        item.origin.path = newPath;
        this.renameFurtherFile(item, oldPath);
        if (item.children) {
          this.changeChildrenNodes(item, data);
        }
      });
    }
  }

  renameFurtherFile(node: NzTreeNode, oldPath: string) {
    let furtherMenu = JSON.parse(localStorage.getItem(Setting.storage.furtherMenu)) || [];
    const updateFile = furtherMenu.find(it => it.id === node.origin.id);
    if (!updateFile) {
      return;
    }
    const data = {
      path: node.origin.path,
      id: node.origin.id,
      content: node.origin.content,
      codeType: node.origin.codeType,
    };
    this.ideService.updateFileForFurther(data).subscribe(res => {
      this.afterRenameMenu.emit({
        path: oldPath,
        name: this.fileName,
        newPath: node.origin.path
      });
      furtherMenu = JSON.parse(localStorage.getItem(Setting.storage.furtherMenu)) || [];
      const newFurtherMenu = furtherMenu.map(item => {
        if (item.id === data.id) {
          item = res;
        }
        return item;
      });
      localStorage.setItem(Setting.storage.furtherMenu, JSON.stringify(newFurtherMenu));
    });
  }

  deleteProjectMenu(event) {
    event.preventDefault();
    const requestData = {
      path: this.insertNode.origin.path
    };
    this.dropdown.close();
    this.insertNode.parentNode ?
      this.insertNode.parentNode.children = this.insertNode.parentNode.children.filter(node => node.key !== this.insertNode.key) :
      this.treeCom.nzNodes = this.treeCom.nzNodes.filter(node => node.key !== this.insertNode.key);
    this.afterDeleteMenu.emit(requestData); // 1.output the delete file path to parent component
    this.deleteFurtherMenu();
  }

  deleteFurtherMenu() {
    const furtherMenuStr = localStorage.getItem(Setting.storage.furtherMenu);
    const furtherMenu = JSON.parse(furtherMenuStr) || [];
    const files = []; // files inside this delete menu
    if (this.insertNode.isLeaf) {
      files.push(this.insertNode.origin.path);
    } else {
      this.getInsideFiles(this.insertNode, files);
    }
    this.insertNode = null;
    files.forEach((filePath: string) => {
      furtherMenu.forEach(menu => {
        if (filePath === menu.path) {
          this.ideService.deleteFurtherFile(menu.id).subscribe(() => {
            const newFurtherMenu = furtherMenu.filter(item => item.id !== menu.id);
            localStorage.setItem(Setting.storage.furtherMenu, JSON.stringify(newFurtherMenu));
          });
        }
      });
    });
  }

  getInsideFiles(node: NzTreeNode, files) {
    if (node.children && node.children.length > 0) {
      node.children.forEach(item => {
        if (item.isLeaf) {
          files.push(item.origin.path);
        } else if (item.children) {
          this.getInsideFiles(item, files);
        }
      });
    }
  }

  afterContentChange(tab: Tab, nodes?: NzTreeNode[]) {
    if (!nodes) {
      nodes = this.treeCom.nzNodes;
    }
    nodes.forEach(node => {
      if (node.isLeaf && node.origin.key === tab.key) {
        node.origin.content = tab.content;
        node.origin.id = tab.id;
      }
      if (node.children) {
        this.afterContentChange(tab, node.children);
      }
    });
  }

  sort() {
    this.insertNode && this.insertNode.children ?
      this.insertNode.children.sort(Util.compare('title', true)) :
      this.treeCom.nzNodes.sort(Util.compare('title', true));
  }
}
